package cn.cas.mango.service;

import cn.cas.mango.dto.HotListDetail;
import cn.cas.mango.dto.Message;
import cn.cas.mango.dto.param.HotListDetailParam;
import cn.cas.mango.dto.param.SearchHotParam;
import cn.cas.mango.service.samenews.SameNewsService;
import cn.cas.mango.util.Matrix;
import cn.cas.mango.util.RenderDateUtil;
import cn.cas.mango.util.RequestUtil.HotListDetailRequest;
import cn.cas.mango.util.ResponseUtil.ResponseUtil;
import cn.cas.mango.util.TimeUtil;
import cn.cas.mango.util.paramCheck.HotListDetailParamCheckUtil;
import cn.cas.mango.util.paramCheck.SearchHotParamCheckUtil;
import cn.cas.mango.util.singlepass.SinglePass;
import cn.cas.mango.util.string.StringUtils;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class HotListDetailSearchService {

  @Autowired
  private HotListDetailParamCheckUtil checkUtil;
  @Autowired
  private ConfigService config;
  @Autowired
  private HotListDetailRequest hotListDetailRequest;


  private Map<String, Hit<HotListDetail>> wrapNewsMap(List<Hit<HotListDetail>> newsList) {
    Map<String, Hit<HotListDetail>> ret = new HashMap<>();
    for (Hit<HotListDetail> hit: newsList) {
      HotListDetail source = hit.source();
      String key = source.getKey();
      ret.put(key, hit);
    }
    return ret;
  }

  private List<List<Hit<HotListDetail>>> getReturnNews(Map<String, Hit<HotListDetail>> newsMap,
                                                       Map<String, List<Hit<HotListDetail>>> sameNewsList,
                                                       int from, int size) {
    List<List<Hit<HotListDetail>>> ret = new ArrayList<>();
    List<String> coreKeyList = new ArrayList<>(sameNewsList.keySet());
    if (from > newsMap.size()) {
      return new ArrayList<>();
    }
    List<String> cutoffList = coreKeyList.subList(from, Math.min(from + size, coreKeyList.size()));
    for (String key: cutoffList) {
      List<Hit<HotListDetail>> temp = new ArrayList<>();
      Hit<HotListDetail> core = newsMap.get(key);
      temp.add(core);
      temp.addAll(sameNewsList.get(key));
      ret.add(temp);
    }
    return ret;
  }

  // 过滤关键词的黑名单
  private void filterBlackList(List<Hit<HotListDetail>> list) {
    for (Hit<HotListDetail> hit: list) {
      HotListDetail source = hit.source();
      List<String> collectKeywords = Sets.difference(ResponseUtil.wrappedKeyword(source.getV_keywords()), config.getBlackList()).stream().collect(Collectors.toList());
      source.setV_keywords(collectKeywords);
    }
  }

  /**
   * 已被废弃
   * @param param
   * @return
   */
  public Message cluster(HotListDetailParam param) {
    checkUtil.check(param);
    log.info("【uuid: {}】, query param: {}", param.getUuid(), param);
    List<Hit<HotListDetail>> newsList = hotListDetailRequest.getNews(param);
    filterBlackList(newsList);
    Map<String, Hit<HotListDetail>> newsMap = wrapNewsMap(newsList);
    TimeUtil timeTotal = new TimeUtil();
    SameNewsService sameNewsService = new SameNewsService(newsList, SortOrder.Desc, "_score");
    Map<String, List<Hit<HotListDetail>>> sameNewsList = sameNewsService.getSameNewsList(param.getLikeDegree());

    log.info("【uuid: {}】, cluster total cost: {}s ", param.getUuid(), timeTotal.getCost());
    JSONObject ret = new JSONObject();
    JSONArray jsonArray = new JSONArray();
    List<List<Hit<HotListDetail>>> returnNews = getReturnNews(newsMap, sameNewsList, param.getFrom(), param.getPageSize());
    int count = 0;
    for (int i = 0; i < returnNews.size(); i++) {
      JSONObject jsonObject = new JSONObject();
      List<Hit<HotListDetail>> hits = returnNews.get(i);
      count += hits.size();
      jsonObject.put("order_of_cluster", i + param.getFrom());
      jsonObject.put("hot", hits.size());
      JSONArray data =  new JSONArray();
      for (Hit<HotListDetail> hit: hits) {
        HotListDetail news = hit.source();
        data.add(wrapReturnNews(news));
      }
      jsonObject.put("data", data);
      jsonArray.add(jsonObject);
    }
    ret.put("data", jsonArray);
    ret.put("from", param.getFrom());
    ret.put("total_news", newsList.size());
    ret.put("return_news", count);
    ret.put("total_cluster", sameNewsList.size());
    ret.put("return_cluster", returnNews.size());

    return Message.builder().code(200).data(ret).build();
  }

  private Map<String, List<Hit<HotListDetail>>> wrapAreaCastNewsMap(List<Hit<HotListDetail>> newsList) {
    Map<String, List<Hit<HotListDetail>>> temp = new HashMap<>();
    for (Hit<HotListDetail> hit: newsList) {
      HotListDetail source = hit.source();
      List<String> subjects = (List<String>) source.getSubject();
      for (String subject: subjects) {
        if (!temp.containsKey(subject)) {
          temp.put(subject, new ArrayList<>());
        }
        temp.get(subject).add(hit);
      }
    }
    Map<String, List<Hit<HotListDetail>>> ret = new LinkedHashMap<>();
    for (String subject: config.getTotalSubject()) {
      if (temp.containsKey(subject)) {
        ret.put(subject, temp.get(subject));
      } else {
        ret.put(subject, new ArrayList<>());
      }
    }
    return ret;
  }

  // 检查subject是否存在，空则置为其他
  private void checkSubject(List<Hit<HotListDetail>> newsList) {
    for (Hit<HotListDetail> hit: newsList) {
      HotListDetail source = hit.source();
      Set<String> strings = ResponseUtil.wrappedKeyword(source.getSubject());
      if (strings.isEmpty()) {
        strings.add("其他");
      }
      List<String> collect = new ArrayList<>(strings);
      source.setSubject(collect);
    }
  }

  /**
   * 先根据关键词聚类，再根据向量聚类，其中subject是娱乐、体育只用关键词聚类（原始数据不存储娱乐、体育的向量）
   * @param param
   * @return
   */
  public Message cluster_vector(HotListDetailParam param) {
    config.getGetAssembleHotNoVectorSubjects().clear();
    config.getGetAssembleHotNoVectorSubjects().addAll(new ArrayList<String>(){{add("娱乐");add("体育");}});
    log.info("no vector field: {}", config.getGetAssembleHotNoVectorSubjects());
    checkUtil.check(param);
    log.info("【uuid: {}】, query param: {}", param.getUuid(), param);
    List<Hit<HotListDetail>> newsList = hotListDetailRequest.getNews(param);
    filterBlackList(newsList);
    checkSubject(newsList);

    Map<String, List<Hit<HotListDetail>>> areaCastNewsMap = wrapAreaCastNewsMap(newsList);
    Map<String, List<List<HotListDetail>>> areaCastClustersMap = new LinkedHashMap<>();
    for (String subject: areaCastNewsMap.keySet()) {
      List<Hit<HotListDetail>> newsListItem = areaCastNewsMap.get(subject);
      Map<String, Hit<HotListDetail>> newsMap = wrapNewsMap(newsListItem);
      TimeUtil timeTotal = new TimeUtil();
      Map<String, List<Hit<HotListDetail>>> sameNewsList = new LinkedHashMap<>();

      if (config.isKeywordsCluster() || config.getGetAssembleHotNoVectorSubjects().contains(subject)) {
        SameNewsService sameNewsService = new SameNewsService(newsListItem, SortOrder.Desc, "_score");
        sameNewsList = sameNewsService.getSameNewsList(param.getLikeDegree());
      } else {
        if (!newsMap.isEmpty()) {
          String core = null;
          for (String key: newsMap.keySet()) {core=key;break;}
          List<Hit<HotListDetail>> temp = new ArrayList<>(newsMap.values());
          Hit<HotListDetail> hotListDetailHit = newsMap.get(core);
          temp.remove(hotListDetailHit);
          sameNewsList.put(core, temp);
        }
      }

      log.info("now subject: {}", subject);
      log.info("【uuid: {}】, keywords cluster total cost: {}s ", param.getUuid(), timeTotal.getCost());

      List<List<Hit<HotListDetail>>> returnNews = getReturnNews(newsMap, sameNewsList, 0, Integer.MAX_VALUE);
      List<List<HotListDetail>> lists = deWrapNews(returnNews);
      List<List<HotListDetail>> clusters;

      if (!config.getGetAssembleHotNoVectorSubjects().contains(subject)) {
        timeTotal.setStart();
        log.info("【uuid: {}】, vector cluster start, aggWeight: {}", param.getUuid(), param.getAggWeight());
        clusters = new SinglePass(lists, param.getAggWeight(), config.getVectorSize()).getClusters();
        log.info("【uuid: {}】, vector cluster total cost: {}s ", param.getUuid(), timeTotal.getCost());
      } else {
        log.info("subject {} in no vector list", subject);
        clusters = lists;
      }
      areaCastClustersMap.put(subject, clusters);
    }

    JSONArray ret = new JSONArray();
    for (String subject: areaCastClustersMap.keySet()) {
      JSONObject jsonObject = new JSONObject();
      List<List<HotListDetail>> clusters = areaCastClustersMap.get(subject);
      jsonObject.put("subject", subject);
      JSONArray jsonArray = new JSONArray();
      for (List<HotListDetail> cluster: clusters) {
        jsonArray.add(wrapList(cluster));
      }
      jsonObject.put("aggs", jsonArray);
      ret.add(jsonObject);
    }


    return Message.builder().code(200).data(ret).build();
  }




  private JSONObject wrapList(List<HotListDetail> list) {
    int hot = list.size() - 1;
    HotListDetail core = list.get(0);
    JSONObject coreObject = wrapCores(core);
    coreObject.put("simHotNum", hot);
    JSONArray jsonArray = new JSONArray();
    List<String> keys = new ArrayList<>();
    for (HotListDetail hotListDetail: list) {keys.add(hotListDetail.getKey());}
    if (keys.size() > 3) {
      log.debug("\nsubject: {}, first title: {}", core.getSubject(), core.getTitle());
      log.debug("\n" + String.join(",", keys));
    }
    for (int i = 1; i < list.size(); i++) {
      HotListDetail now = list.get(i);
      jsonArray.add(wrapCores(now));
    }

    coreObject.put("agg", jsonArray);
    return coreObject;
  }


  private JSONObject wrapCores(HotListDetail core) {
    String title = core.getTitle();
    String key = core.getKey();
    Object platform = core.getPlatform();
    String platformName = core.getPlatformName();
    Integer maxHotValue = core.getMaxHotValue();
    Integer duration = core.getDuration();
    Integer maxRankTop = core.getMaxRankTop();
    Object subject = core.getSubject();
    String  insertTime = core.getInsert_time();
    JSONArray geo = new JSONArray();
    Object domainLabel = core.getV_ssly() == null ? new JSONArray(): core.getV_ssly();
    JSONObject coreObject = new JSONObject();
    coreObject.put("title", title);
    coreObject.put("key", key);
    coreObject.put("platformCode", platform);
    coreObject.put("platformName", platformName);
    coreObject.put("maxHotValueFormat", ResponseUtil.wrapMaxHotValueFormat(maxHotValue == null ? 1 : maxHotValue));
    coreObject.put("duration", ResponseUtil.wrapDuration(duration == null ? 1 : duration));
    coreObject.put("maxRankTop", maxRankTop == null ? 1 : maxRankTop);
    List<String> strings = new ArrayList<>(ResponseUtil.wrappedKeyword(subject));
    coreObject.put("subject", strings.isEmpty() ? "": strings.get(0));
    coreObject.put("firstInsertTime", insertTime);
    coreObject.put("geoLabel", geo);
    coreObject.put("sim", 1.0);
    coreObject.put("domainLabel", domainLabel);
//    coreObject.put("keywords", core.getV_keywords());
    return coreObject;
  }



  private List<List<HotListDetail>> deWrapNews(List<List<Hit<HotListDetail>>> news) {
    List<List<HotListDetail>> ret = new ArrayList<>();
    for (List<Hit<HotListDetail>> hits : news) {
      List<HotListDetail> collect = hits.stream().map(x -> x.source()).collect(Collectors.toList());
      ret.add(collect);
    }
    return ret;
  }

  private JSONObject wrapReturnNews(HotListDetail news) {
    String key = news.getKey();
    Set<String> strings = ResponseUtil.wrappedKeyword(news.getV_keywords());
    String keywords = "[" + String.join(", ", strings) + "]";
    String insertTime = news.getInsert_time();
    Object platform = news.getPlatform();
    String title = news.getTitle();
    String desc  = news.getDesc();
    JSONObject temp = new JSONObject();
    temp.put("key",  key);
//    temp.put("keywords", keywords);
    temp.put("insertTime", insertTime);
    temp.put("platform", platform);
    temp.put("title", title);
    temp.put("desc", desc);
    return temp;
  }

}
